<template>
  <div class="uploadDiv">
    <a-spin
      :spinning="uploadSpinning"
      style="min-height: 80px; max-height: none"
    >
      <a-upload
        :file-list="fileList"
        :multiple="false"
        :before-upload="beforeUpload"
        @remove="handleRemove"
      >
        <a-button>
          <upload-outlined></upload-outlined>
          选择文件
        </a-button>
      </a-upload>
      <a-button
        type="primary"
        :disabled="fileList.length === 0 || !finishSlice"
        :loading="uploading"
        style="margin-top: 16px"
        @click="handleUpload"
      >
        {{ uploading ? "上传中" : "开始上传" }}
      </a-button>
    </a-spin>
    <a-progress
      :percent="Math.round((sliceProgress / sliceCount) * 100)"
      :status="sliceProgress === sliceCount ? 'success' : 'active'"
      v-if="showSliceProgress"
    />
    <a-progress
      :percent="Math.round((finishCount / sliceCount) * 100)"
      :status="finishCount === sliceCount ? 'success' : 'active'"
      v-if="showProgress"
    />
  </div>
  <div class="bodyDiv">主题文字</div>
</template>
<script setup lang="ts">
import SparkMD5 from "spark-md5";
import axios from "axios";
import { message } from "ant-design-vue";
import { onMounted, ref } from "vue";
import { saveAs } from "file-saver";
// 文件列表
const fileList = ref<File[]>([]);
// 上传状态
const uploading = ref<boolean>(false);
// 分片完成情况
const finishSlice = ref<boolean>(false);
// 完成上传的分片数量
const finishCount = ref<number>(0);
// 展示上传进度条
const showProgress = ref<boolean>(false);
// 切片数量
const sliceCount = ref<number>(0);
// 切片进度条
const sliceProgress = ref<number>(0);
// 上传失败的数量
const errorCount = ref<number>(0);
// 展示切片进度条
const showSliceProgress = ref<boolean>(false);
const uploadSpinning = ref<boolean>(false);

// 切片列表
let fileChunkList: any = [];
// 发送的切片数量
const sendCount = ref<number>(0);
// 文件类型
let filetype = "";
// 文件名
let filename = "";
// 文件hash值
let hash = "";
const beforeUpload = async (file) => {
  // message.info("开始文件切片");
  // 显示切片进度条
  showSliceProgress.value = true;
  uploadSpinning.value = true;
  // 文件添加到文件列表 这里只展示单文件上传
  fileList.value.push(file);

  let result = await sectionFn(file);
  console.log(result);
  return false;
};

const sectionFn = (file) => {
  // 一些参数的初始化
  fileChunkList = [];
  finishSlice.value = false;
  finishCount.value = 0;
  sliceProgress.value = 0;
  showProgress.value = false;
  sliceCount.value = 0;
  errorCount.value = 0;

  return new Promise((resolve, reject) => {
    // 初始化md5工具对象
    const spark = new SparkMD5.ArrayBuffer();
    // 用于读取文件计算md5
    const fileReader = new FileReader();
    // 这里是依据.来对文件和类型进行分割
    let fileInfo = file.name.split(".");
    filename = fileInfo[0];
    // 最后一个.之前的内容都应该认定为文件名称
    if (fileInfo.length > 1) {
      filetype = fileInfo[fileInfo.length - 1];
      for (let i = 1; i < fileInfo.length - 1; i++) {
        filename = filename + "." + fileInfo[i];
      }
    }
    // 这里开始做切片
    // 设置切片大小 可以根据实际情况设置
    const chunkSize = 1024 * 1024 * 1;
    // 计算出切片数量
    sliceCount.value = Math.ceil(file.size / chunkSize);
    let curChunk = 0;
    // 切片操作的实际方法【定义】
    const sliceNext = () => {
      // 使用slice方法进行文件切片
      const chunkFile = file.slice(curChunk, curChunk + chunkSize);
      // console.log(chunkFile);

      // 读取当前切片文件流【这里会触发onload方法】
      fileReader.readAsArrayBuffer(chunkFile);
      // 加入切片列表
      fileChunkList.push({
        // 切片文件信息
        chunk: chunkFile,
        // 文件名
        filename: filename,
        // 分片索引 这里直接借助sliceProgress来实现
        seq: sliceProgress.value + 1,
        // 文件类型
        type: filetype,
        // 状态信息 用于标识是否上传成功
        status: false,
      });
      // 切片完成变量自增
      sliceProgress.value++;
    };

    // 进入方法需要进行首次切片操作
    sliceNext();

    // 读取文件流时会触发onload方法
    fileReader.onload = (e: any) => {
      // console.log(file);

      // 将文件流加入计算md5
      spark.append(e.target.result);
      // 修改切片位移
      curChunk += chunkSize;
      // 说明还没到达最后一个切片 继续切
      if (sliceProgress.value < sliceCount.value) {
        sliceNext();
      } else {
        // 说明切片完成了
        finishSlice.value = true;
        uploadSpinning.value = false;
        // 读取文件hash值
        hash = spark.end();
        message.success("文件分片完成");
        // 将哈希值作为其中一个属性 写入到分片列表中
        fileChunkList.forEach((content: any) => {
          content.hash = hash;
        });
        resolve(fileChunkList);
      }
    };
  });
};

// 开始执行上传切片逻辑
const startUpload = () => {
  return new Promise((resolve, reject) => {
    const next = () => {
      // 递归出口 分片上传完毕
      if (finishCount.value + errorCount.value >= sliceCount.value) {
        return;
      }
      // 记录当前遍历位置
      let cur = sendCount.value++;
      // 说明越界了 直接退出
      if (cur >= sliceCount.value) {
        return;
      }
      // 获取分片信息
      let content = fileChunkList[cur];
      // 已经上传过了 直接跳过【可用于断点续传】
      if (content.status === true) {
        if (finishCount.value + errorCount.value < sliceCount.value) {
          next();
          return;
        }
      }
      // 开始填充上传数据 这里需要使用FormData来存储信息
      const formData = new FormData();
      formData.append("file", content.chunk);
      formData.append("hash", content.hash);
      formData.append("filename", content.filename);
      formData.append("seq", content.seq);
      formData.append("type", content.type);
      // 开始上传
      axios
        .post("http://192.168.1.167:3000/upload", formData)
        .then((res) => {
          // 接收回调信息
          const data = res.data;
          if (data.success) {
            // 成功计数 并设置分片上传状态
            finishCount.value += 1;
            content.status = true;
          } else {
            // 失败计数
            errorCount.value += 1;
          }
          // 说明完成最后一个分片上传但上传期间出现错误
          if (
            errorCount.value !== 0 &&
            errorCount.value + finishCount.value === sliceCount.value
          ) {
            message.error("上传发生错误，请重传");
            showProgress.value = false;
            uploading.value = false;
          }
          // 说明还有分片未上传 需要继续递归
          if (finishCount.value + errorCount.value < sliceCount.value) {
            next();
          }
          // 说明所有分片上传成功了 发起合并操作
          if (finishCount.value === sliceCount.value) {
            merge();
          }
        })
        .catch((error) => {
          // 对于图中发生的错误需要捕获并记录
          errorCount.value += 1;
          if (
            errorCount.value !== 0 &&
            errorCount.value + finishCount.value === sliceCount.value
          ) {
            message.error("上传发生错误，请重传");
            showProgress.value = false;
            uploading.value = false;
          }
          // 当前分片上传失败不应影响下面的分片
          if (finishCount.value + errorCount.value < sliceCount.value) {
            next();
          }
          console.log(error);
        });
    };
    // 只允许同时10个任务在等待
    while (sendCount.value < 10 && sendCount.value < sliceCount.value) {
      next();
    }
  });
};

const merge = () => {
  message.success("上传成功，等待服务器合并文件");
  // 发起合并请求 传入文件hash值、文件类型、文件名
  axios
    .post("http://192.168.1.167:3000/merge", {
      hash: hash,
      type: filetype,
      filename: filename,
    })
    .then((res) => {
      const data = res.data;
      if (data.success) {
        message.success(data.message);
        // 获取上传成功的文件地址
        console.log(data.content);
        // 其他业务操作...
      } else {
        message.error(data.message);
      }
      uploading.value = false;
    })
    .catch((e) => {
      message.error("发生错误了");
      uploading.value = false;
    });
};

const handleUpload = async () => {
  startUpload();
};

// const onslice = () => {
//   fileList.value.forEach(async (item) => {
//     let result = await sectionFn(item);
//     console.log(result);
//   });
// };

// 删除

const handleRemove = (file: File) => {
  const index = fileList.value.indexOf(file);
  const newFileList = fileList.value.slice();
  let hash = "";
  newFileList.splice(index, 1);
  fileList.value = newFileList;
  // 取消之后需要进行相关变量的重新初始化
  fileChunkList = [];
  finishSlice.value = false;
  finishCount.value = 0;
  sliceProgress.value = 0;
  showProgress.value = false;
  sliceCount.value = 0;
  errorCount.value = 0;
};

const donwloadFn = (url, params, filename) => {
  // let data=
  // const blob = new Blob([data]);
  saveAs("", "");
};
</script>
<style scoped lang="scss">
.uploadDiv {
  width: 100%;
  background: #fff;
  padding: 30px;
}
.bodyDiv {
  background-color: var(--bg-color);
  color: var(--text-color);
}
</style>
